
package messaging;

import java.io.Serializable;
import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;

import node.NodeManager;

/**
 * This class represents the channels that each node listens to on its receiving end.
 * It stored the node id, 
 * An array list of all nodes from which this node receives messages,
 * a hash map that stores the node number from which this node
 * is going to receive message along with the  channel queue for each receiving channel.
 * 
 * @author Team 4
 * @version Spring 2013
 * 
 */
@SuppressWarnings("serial")
public class MessageChannels implements Serializable
{
  /**
   * The node number for this message channel.
   */
  private final int my_node_num;
  /**
   * Reference to NodeManager.
   */
  private final NodeManager nodeman;
  /**
   * Array list of message queues for this node.
   */
  public HashMap <Integer,ChannelQueue> channelset;
  /**
   * An array list of incoming connections for this node.
   */
  public ArrayList<Integer> incoming_channels;
  /**
   * Instantiates the node number.
   * 
   * @param the_node_no : stores the node number
   * @throws NotBoundException 
   * @throws RemoteException 
   * @throws AccessException 
   */
  public MessageChannels(final int the_node_no,NodeManager nm) throws 
  AccessException, RemoteException, NotBoundException
  {
    this.my_node_num = the_node_no;
    this.nodeman = nm;
    channelset = new HashMap<Integer, ChannelQueue>();
    incoming_channels = new ArrayList<Integer>();
    incoming_channels = this.incomingConnections(my_node_num);
  }
  /**
   * This method will look up the registry and makes an array list of all the nodes to which 
   * this needs to communicate.
   * @return ArrayList of the the neighbors that this node can talk to.
   * @throws NotBoundException 
   * @throws RemoteException 
   * @throws AccessException 
   */
  public ArrayList<Integer> get_my_neighbours(int my_node_num) throws 
  AccessException, RemoteException, NotBoundException
      {
    double power = Math.log10(nodeman.total_nodes_chord) / Math.log10(2);
    power = (int) power;
    final List<Integer> to_return = new ArrayList<Integer>();
    int node_num_to_add;

    for (int i = 0; i < power; i++)
    {
      node_num_to_add = 
          (my_node_num + (int) Math.pow(2, i)) % nodeman.total_nodes_chord;  
      to_return.add(node_num_to_add);
    }
    return (ArrayList<Integer>) to_return;    
  }
  /**
   * Given a node number , this method will find what other nodes will
   *  communicate with this node.
   * Useful for queue processing.
   * @throws NotBoundException 
   * @throws RemoteException 
   * @throws AccessException 
   */
  public ArrayList<Integer> incomingConnections(int the_node_num) throws 
  AccessException, RemoteException, NotBoundException
  {
    double power = Math.log10(nodeman.total_nodes_chord) / Math.log10(2);
    power = (int) power;
    final List<Integer> to_return = new ArrayList<Integer>();
    for (int i = 0; i < power; i++)
    {
      int node_to_add = (the_node_num - (int) Math.pow(2, i)) % nodeman.total_nodes_chord;
      if (node_to_add < 0)
      {
        node_to_add = node_to_add + nodeman.total_nodes_chord;
      }
      to_return.add(node_to_add);   
    }
    return (ArrayList<Integer>) to_return;   
  }
  /**
   * This method will open a blocking queue for each channel for message
   * passing. It will populate the hashMap that stores the connections and create the
   * channels with id representing the destination node that this channel takes
   * the message to.
   * @throws NotBoundException 
   * @throws RemoteException 
   * @throws AccessException 
   */
  public void createChannels() throws AccessException, RemoteException, NotBoundException
  {
    List<Integer> my_connections = new ArrayList<Integer>();
    my_connections = this.incomingConnections(my_node_num);
    Iterator<Integer> itr = my_connections.iterator();
    while (itr.hasNext())
    {
      final int destnode = itr.next();
      final ChannelQueue message_channel = new ChannelQueue(destnode , my_node_num);
      this.channelset.put(destnode, message_channel);
    }
  }
  /**
   * This method will return the Channel queue corresponding to a given destination node.
   * @param originating_node -
   *  the node from which the channel queue begins to terminate in this node. 
   *  @return - ChannelQueue corresponding to the originating_node terminating in
   *  this node.
   */
  public ChannelQueue getChannelQueue(final int originating_node)
  {
    return this.channelset.get(originating_node);
  }
  /**
   * this method returns a arraylist of incoming blocking queues for the this node.
   * @return ArrayList - list of blocking queues for this node id.
   */
  public ArrayList<BlockingQueue<InterNodeMessage>> getBlockingQueue()
  {
    ArrayList<BlockingQueue<InterNodeMessage>> to_return =
        new ArrayList<BlockingQueue<InterNodeMessage>>();
    final Iterator itr = this.incoming_channels.iterator();
    while (itr.hasNext())
    {
      to_return.add(this.channelset.get((int) itr.next()).getQueue());
    }
    return to_return;
  }
}
